/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.activemq.artemis.utils; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicLong; import org.jboss.logging.Logger; public class SoftValueHashMap<K, V extends SoftValueHashMap.ValueCache> implements Map<K, V> { private static final Logger logger = Logger.getLogger(SoftValueHashMap.class); // The soft references that are already good. // too bad there's no way to override the queue method on ReferenceQueue, so I wouldn't need this private final ReferenceQueue<V> refQueue = new ReferenceQueue<>(); private final Map<K, AggregatedSoftReference> mapDelegate = new HashMap<>(); private final AtomicLong usedCounter = new AtomicLong(0); private int maxElements; // Constants ----------------------------------------------------- // Attributes ---------------------------------------------------- // Static -------------------------------------------------------- public interface ValueCache { boolean isLive(); } // Constructors -------------------------------------------------- public SoftValueHashMap(final int maxElements) { this.maxElements = maxElements; } // Public -------------------------------------------------------- public void setMaxElements(final int maxElements) { this.maxElements = maxElements; checkCacheSize(); } public int getMaxEelements() { return this.maxElements; } /** * @see java.util.Map#size() */ @Override public int size() { processQueue(); return mapDelegate.size(); } /** * @see java.util.Map#isEmpty() */ @Override public boolean isEmpty() { processQueue(); return mapDelegate.isEmpty(); } /** * @param key * @see java.util.Map#containsKey(java.lang.Object) */ @Override public boolean containsKey(final Object key) { processQueue(); return mapDelegate.containsKey(key); } /** * @param value * @see java.util.Map#containsValue(java.lang.Object) */ @Override public boolean containsValue(final Object value) { processQueue(); for (AggregatedSoftReference valueIter : mapDelegate.values()) { V valueElement = valueIter.get(); if (valueElement != null && value.equals(valueElement)) { return true; } } return false; } /** * @param key * @see java.util.Map#get(java.lang.Object) */ @Override public V get(final Object key) { processQueue(); AggregatedSoftReference value = mapDelegate.get(key); if (value != null) { value.used(); return value.get(); } else { return null; } } /** * @param key * @param value * @see java.util.Map#put(java.lang.Object, java.lang.Object) */ @Override public V put(final K key, final V value) { processQueue(); AggregatedSoftReference newRef = createReference(key, value); AggregatedSoftReference oldRef = mapDelegate.put(key, newRef); checkCacheSize(); newRef.used(); if (oldRef != null) { return oldRef.get(); } else { return null; } } private void checkCacheSize() { if (maxElements > 0 && mapDelegate.size() > maxElements) { TreeSet<AggregatedSoftReference> usedReferences = new TreeSet<>(new ComparatorAgregated()); for (AggregatedSoftReference ref : mapDelegate.values()) { V v = ref.get(); if (v != null && !v.isLive()) { usedReferences.add(ref); } } for (AggregatedSoftReference ref : usedReferences) { if (ref.used > 0) { Object removed = mapDelegate.remove(ref.key); if (logger.isTraceEnabled()) { logger.trace("Removing " + removed + " with id = " + ref.key + " from SoftValueHashMap"); } if (mapDelegate.size() <= maxElements) { break; } } } } } class ComparatorAgregated implements Comparator<AggregatedSoftReference> { @Override public int compare(AggregatedSoftReference o1, AggregatedSoftReference o2) { long k = o1.used - o2.used; if (k > 0) { return 1; } else if (k < 0) { return -1; } k = o1.hashCode() - o2.hashCode(); if (k > 0) { return 1; } else if (k < 0) { return -1; } else { return 0; } } } /** * @param key * @see java.util.Map#remove(java.lang.Object) */ @Override public V remove(final Object key) { processQueue(); AggregatedSoftReference ref = mapDelegate.remove(key); if (ref != null) { return ref.get(); } else { return null; } } /** * @param m * @see java.util.Map#putAll(java.util.Map) */ @Override public void putAll(final Map<? extends K, ? extends V> m) { processQueue(); for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { put(e.getKey(), e.getValue()); } } /** * @see java.util.Map#clear() */ @Override public void clear() { mapDelegate.clear(); } /** * @see java.util.Map#keySet() */ @Override public Set<K> keySet() { processQueue(); return mapDelegate.keySet(); } /** * @see java.util.Map#values() */ @Override public Collection<V> values() { processQueue(); ArrayList<V> list = new ArrayList<>(); for (AggregatedSoftReference refs : mapDelegate.values()) { V value = refs.get(); if (value != null) { list.add(value); } } return list; } /** * @see java.util.Map#entrySet() */ @Override public Set<java.util.Map.Entry<K, V>> entrySet() { processQueue(); HashSet<Map.Entry<K, V>> set = new HashSet<>(); for (Map.Entry<K, AggregatedSoftReference> pair : mapDelegate.entrySet()) { V value = pair.getValue().get(); if (value != null) { set.add(new EntryElement<>(pair.getKey(), value)); } } return set; } /** * @param o * @see java.util.Map#equals(java.lang.Object) */ @Override public boolean equals(final Object o) { processQueue(); return mapDelegate.equals(o); } /** * @see java.util.Map#hashCode() */ @Override public int hashCode() { return mapDelegate.hashCode(); } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- // Private ------------------------------------------------------- @SuppressWarnings("unchecked") private void processQueue() { AggregatedSoftReference ref = null; while ((ref = (AggregatedSoftReference) this.refQueue.poll()) != null) { logger.tracef("Removing reference through processQueue:: %s", ref.get()); mapDelegate.remove(ref.key); } } private AggregatedSoftReference createReference(final K key, final V value) { AggregatedSoftReference ref = new AggregatedSoftReference(key, value); return ref; } // Inner classes ------------------------------------------------- class AggregatedSoftReference extends SoftReference<V> { final K key; long used = 0; public long getUsed() { return used; } public void used() { used = usedCounter.incrementAndGet(); } AggregatedSoftReference(final K key, final V referent) { super(referent, refQueue); this.key = key; } @Override public String toString() { return "AggregatedSoftReference [key=" + key + ", used=" + used + "]"; } } static final class EntryElement<K, V> implements Map.Entry<K, V> { final K key; volatile V value; EntryElement(final K key, final V value) { this.key = key; this.value = value; } /* (non-Javadoc) * @see java.util.Map.Entry#getKey() */ @Override public K getKey() { return key; } /* (non-Javadoc) * @see java.util.Map.Entry#getValue() */ @Override public V getValue() { return value; } /* (non-Javadoc) * @see java.util.Map.Entry#setValue(java.lang.Object) */ @Override public V setValue(final V value) { this.value = value; return value; } } }